package com.netflexitysolutions.amazonws.sdb.orm.internal.operations;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.concurrent.Callable;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.netflexitysolutions.amazonws.sdb.Domain;
import com.netflexitysolutions.amazonws.sdb.Item;
import com.netflexitysolutions.amazonws.sdb.SimpleDBException;
import com.netflexitysolutions.amazonws.sdb.orm.SimpleDBSession;
import com.netflexitysolutions.amazonws.sdb.orm.SimpleDBSessionListener;
import com.netflexitysolutions.amazonws.sdb.orm.metadata.ItemMetadata;

/**
 * @author netflexity
 *
 */
public class Save<T> implements Callable<T> {
    private static Log logger = LogFactory.getLog(Save.class.getName());
    
    private SimpleDBSession session;
    private T record;
    private String id;
    
    /**
     * @param session
     * @param record
     */
    public Save(SimpleDBSession session, T record) {
        this.session = session;
        this.record = record;
    }

    /* (non-Javadoc)
     * @see java.util.concurrent.Callable#call()
     */
    public T call() throws Exception {
        try {
            persist(record);
        } 
        catch (Exception e) {
            logger.error(e);
            throw e;
        }
        return record;
    }

    /**
     * @param record
     * @throws SDBException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws S3ServiceException
     * @throws IOException
     */
    protected void persist(T record) throws SimpleDBException, IllegalAccessException, InvocationTargetException, IOException {
    	long start = System.currentTimeMillis();
        if(logger.isDebugEnabled()){
        	logger.debug("Save began at " + (start));
        }
        
        // Obtain domain.
        Domain domain = session.getDomain(record.getClass());
        assert(domain != null);
        
        // Obtain metadata for this record type.
        ItemMetadata itemMetadata = session.getMetadata(record.getClass());
        assert(itemMetadata != null);
        
        // Before save.
		List<SimpleDBSessionListener> listeners = session.getFactory().getEntityInterceptors();
		for (SimpleDBSessionListener simpleDBSessionListener : listeners) {
			simpleDBSessionListener.beforeSave(record, null);
		}
		
        // Create appropriate Amazon SDB attributes.
        SimpleDBSaveItemSetter<T> setter = new SimpleDBSaveItemSetter<T>();
        setter.setItemMetadata(itemMetadata);
        id = setter.setValue(record);
        assert(StringUtils.isNotBlank(id));
        
        // Create item based on the id.
        Item item = new Item();
        item.setIdentifier(id);
        
        // Store attributes in SimpleDB.
        Item itemToSave = setter.getChangedItemAttributes().get(id);
        if(itemToSave != null){
        	long start2 = System.currentTimeMillis();
	        session.getFactory().getDataSource().addOrUpdateItem(domain, itemToSave);
	        long duration2 = System.currentTimeMillis() - start2;
	        if(logger.isDebugEnabled()){
	        	logger.debug("Saved item (" + itemToSave.getIdentifier()  + ") attributes for domain " + domain.getName() + ") in " + duration2);
	        }
        }
        
        // Store attributes in SimpleDB.
        Item itemToDelete = setter.getNullableItemAttributes().get(id);
        if(itemToDelete != null){
	        long start2 = System.currentTimeMillis();
	        session.getFactory().getDataSource().deleteItem(domain, itemToDelete);
	        long duration2 = System.currentTimeMillis() - start2;
	        if(logger.isDebugEnabled()){
	        	logger.debug("Removed item (" + itemToDelete.getIdentifier() + ") attributes for domain " + domain.getName() + " in " + duration2);
	        }
        }
        
        // Add to cache.
        session.cachePut(record);
        
        // After save.
        for (SimpleDBSessionListener simpleDBSessionListener : listeners) {
			simpleDBSessionListener.afterSave(record);
		}
        
        if(logger.isDebugEnabled()){
        	logger.debug("Save completed in " + (System.currentTimeMillis() - start));
        }
    }
}
